#ifndef _sdl_debug_h_
#define _sdl_debug_h_

#include "sdl_exceptions.h"
#include "sdl_prim.h"
#include "sdl_singleton.h"
#include <list>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>

#ifndef sdl_default_debug_file
#define sdl_default_debug_file "nkfsdl_debug.txt"
#endif

#ifndef sdl_a_lot
#define sdl_a_lot	10000
#endif

namespace sdl_debug {

    ///różne rodzaje wyjścia debugowania

    typedef enum {
        ostdout, ///<standardowe wyjście
        ofile, ///<wyjście do domyślnego pliku
        ocustom, ///<wyjście do strumienia zdefiniowanego przez użytkownika
        osstream, ///<wyjście do zdefiniowanego stringstream
        ounspecified ///<nieokreślone (decyduje klasa debug)
    } debug_method;

    /**
     * \brief Kanały debugowania
     *
     * Ustalamy następującą hierarchię poziomów wiadomości:
     * 	[0-5]	-	wiadomości, których może być bardzo dużo, wyświetlane co każdą klatkę
     * 	[6-8]	-	inne wiadomości, których może być bardzo dużo (pochodzące z fabryk, tworzenia wielu obiektów naraz itd., pomniejsze konstruktory i destruktory, ładowanie fragmentarycznych plików)
     * 	[9]	-	wiadomości z destruktorów ważniejszych obiektów
     * 	[10]	-	konstruktory ważniejszych obiektów
     * 	[11-20]	-	mniejsze rangą wiadomości informacyjne pochodzące z funkcji wywoływanych jednokrotnie bądź bardzo rzadko, informacje o ładowaniu ważniejszych
     * 	[21-30] -	j.w. dla ważniejszych wiadomości (np. w jakim trybie wideo uruchamiamy obraz)
     * 	[31,+inf] -	ważne wiadomości
     */

    struct chan {
        ///nazwa kanału debugowania
        std::string name;
        ///dolny poziom filtrowania wiadomości
        int lowpass;
        ///dokąd ma być kierowane wyjście z tego kanału
        debug_method out_method;
        ///strumień wyjścia dla danego kanału (jeżeli NULL, to określony odgórnie)
        std::ostream * output;
        chan(const std::string &, const int &, const debug_method &);
        ///predykat do przeszukiwania listy
        bool operator==(const chan & s);
    };

    /**
     * Menadżer odpowiedzialny za rozgłaszanie i filtrowanie wiadomości na kanały
     */

    class debug : public sdl_singleton<debug>, public sdl_prim {
        ///kanały
        std::list<chan> channels;
        ///domyślna globalna metoda wyjścia (o ile kanały nie określają)
        debug_method out_method;
        ///domyślny globalny strumień wyjścia (o ile kanały nie określają)
        std::ostream * global_output;
        ///globalny strumień plikowy
        std::ofstream output_f;
        ///globalny strumień użytkownika
        std::ostream * output_c;
        ///globalny stringstream
        std::stringstream output_ss;
        ///globalny dolny poziom filtrowania wiadomości
        int lowpass;
        bool suppress;

    public:

        debug();
        ~debug();
        /**
         * \brief wydrukowanie (bądź nie) właściwej wiadomości, obsługiwane przez makro
         * @param ch nazwa kanału
         * @param level poziom wiadomości w kanale (wyższy - ważniejsza)
         * @param message wiadomość
         * @param file nazwa pliku źródłowego
         * @param loc	linia kodu w pliku
         */
        void message(const std::string & ch, int level, const std::string & message, const std::string & file, const int & loc);
        ///to samo co message, ale wywala wyjątek
        void error(const std::string & ch, int level, const std::string & message, const std::string & file, const int & loc);
        void set_custom_output(std::ostream * o);
        void set_custom_output(const std::string & ch, std::ostream * o);
        void set_output_method(debug_method m);
        void set_output_method(const std::string & ch, const debug_method & method);
        void set_lowpass(int l);
        void set_lowpass(const std::string & ch, int l);
        void disable_messages();
        void enable_messages();
        bool disabled();
        /**
         * wyjmuje albo tworzy kanał
         */
        chan & get_channel(const std::string & s);
        std::ostream * get_output();
        std::stringstream * get_output_ss();
        std::ofstream * get_output_f();
    };



    /**
     * \brief właściwe makro do głoszenia wiadomości
     * @param c kanał
     * @param l poziom
     * @param m wiadomość, może być w formie strumienia np. ( a << " coś " << b )
     */
#ifdef __sdl_debug
#define	_msg(c,l,m)	\
	{ \
		if(give<sdl_debug::debug>()){ \
			if(!give<sdl_debug::debug>()->disabled()){ \
				std::stringstream mesg; \
				mesg << m ; \
				give<sdl_debug::debug>()->message((c),(l),(mesg.str()),std::string(__FILE__),__LINE__); } }\
	}

#else
#define _msg(c,l,m) {}
#endif

    ///to samo co message, ale wywala wyjątek i pozostaje zdefiniowane w wersji debug
#define	_err(c,m)	\
	{ \
		std::stringstream mesg;\
		mesg << m ;\
		if(give<sdl_debug::debug>()){\
			give<sdl_debug::debug>()->error((c),(sdl_a_lot),(mesg.str()),std::string(__FILE__),__LINE__);}\
		else throw sdlex::exception("m");\
	}

}; //namespace sdl_debug;


#endif //_sdl_debug_h_
